import tkinter as tk
from tkinter import messagebox, simpledialog, ttk, filedialog
from tkcalendar import DateEntry
import sqlite3
from datetime import datetime
import os
import json
import sys
import logging
from ftplib import FTP
import tkinter.font as tkFont

try:
    import pandas as pd
except ImportError:
    messagebox.showerror("错误", "请先安装pandas库！")
    sys.exit(1)

try:
    from cryptography.fernet import Fernet
except ImportError:
    messagebox.showerror("错误", "请先安装cryptography库！")
    sys.exit(1)

# 日期格式常量
DATE_FORMAT = "%Y-%m-%d"
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"

# 配置文件路径
CONFIG_FILE = "config.json"
FTP_CONFIG_FILE = "ftp_config.json"
KEY_FILE = "ftp_key.key"

# 当前版本号
CURRENT_VERSION = "2.20"

# 默认配置
DEFAULT_CONFIG = {
    "width": 935,
    "height": 616,
    "theme": "default",
    "pane_positions": {
        "main_pane": [200, 400],
        "top_pane": [300],
        "middle_pane": [350],
        "equipment_pane": [200]
    },
    "tree_columns": {
        "Serial": 50,
        "Region": 100,
        "Factory": 100,
        "LineCode": 100,
        "Equipment": 200,
        "InstallTime": 100,
        "AddTime": 100,
        "Remarks": 100
    }
}

# 日志配置
logging.basicConfig(
    filename='app.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    encoding='utf-8',
    filemode='a'
)
def load_config():
    try:
        if os.path.exists(CONFIG_FILE):
            with open(CONFIG_FILE, "r", encoding='utf-8') as f:
                config = json.load(f)
                # 使用默认配置补充缺失的配置项
                for key, value in DEFAULT_CONFIG.items():
                    if key not in config:
                        config[key] = value
                    elif isinstance(value, dict):
                        for sub_key, sub_value in value.items():
                            if sub_key not in config[key]:
                                config[key][sub_key] = sub_value
                return config
        return DEFAULT_CONFIG.copy()
    except Exception as e:
        logging.error(f"加载配置失败: {e}")
        return DEFAULT_CONFIG.copy()

def save_config(config):
    try:
        with open(CONFIG_FILE, "w", encoding='utf-8') as f:
            json.dump(config, f, indent=4, ensure_ascii=False)
        logging.info("配置保存成功")
    except Exception as e:
        logging.error(f"保存配置失败: {e}")

# 生成或加载加密密钥
def generate_or_load_key():
    if os.path.exists(KEY_FILE):
        with open(KEY_FILE, "rb") as key_file:
            return key_file.read()
    else:
        key = Fernet.generate_key()
        with open(KEY_FILE, "wb") as key_file:
            key_file.write(key)
        return key

def load_ftp_config():
    try:
        key = generate_or_load_key()
        cipher = Fernet(key)
        if os.path.exists(FTP_CONFIG_FILE):
            with open(FTP_CONFIG_FILE, "r") as f:
                config = json.load(f)
                defaults = {"host": "", "username": "", "password": "", "port": 21, "path": "/equipment"}
                loaded_config = {**defaults, **config}
                if loaded_config["password"]:
                    loaded_config["password"] = cipher.decrypt(loaded_config["password"].encode()).decode()
                return loaded_config
        return {"host": "", "username": "", "password": "", "port": 21, "path": "/equipment"}
    except Exception as e:
        logging.error(f"加载FTP配置失败: {e}")
        return {"host": "", "username": "", "password": "", "port": 21, "path": "/equipment"}

def save_ftp_config(config):
    try:
        key = generate_or_load_key()
        cipher = Fernet(key)
        encrypted_password = cipher.encrypt(config["password"].encode()).decode()
        config_to_save = {**config, "password": encrypted_password}
        with open(FTP_CONFIG_FILE, "w") as f:
            json.dump(config_to_save, f)
        logging.info("FTP配置已保存（密码已加密）")
    except Exception as e:
        logging.error(f"保存FTP配置失败: {e}")

def test_ftp_connection(host, username, password, port, path):
    try:
        ftp = FTP()
        ftp.connect(host, int(port))
        ftp.login(username, password)
        ftp.cwd(path)
        ftp.quit()
        return True, "FTP连接成功且路径有效"
    except Exception as e:
        return False, f"FTP连接失败或路径无效: {e}"
class DatabaseManager:
    def __init__(self):
        try:
            self.conn = sqlite3.connect('equipment_info.db', timeout=20)
            self.conn.execute("PRAGMA foreign_keys = ON")
            self.cursor = self.conn.cursor()
            self.create_tables()
            self.migrate_database()
        except sqlite3.Error as e:
            logging.error(f"数据库连接失败: {e}")
            raise

    def create_tables(self):
        self.cursor.executescript(''' 
            CREATE TABLE IF NOT EXISTS equipment (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                region TEXT,
                factory TEXT,
                production_line_code TEXT,
                install_time TEXT,
                add_time TEXT,
                remarks TEXT
            );
            CREATE TABLE IF NOT EXISTS regions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT UNIQUE
            );
            CREATE TABLE IF NOT EXISTS factories (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                region_name TEXT,
                name TEXT,
                UNIQUE(region_name, name)
            );
            CREATE TABLE IF NOT EXISTS equipment_details (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                equipment_id INTEGER,
                name TEXT,
                quantity INTEGER,
                FOREIGN KEY (equipment_id) REFERENCES equipment(id)
            );
        ''')
        self.conn.commit()
        logging.info("数据库表检查或创建完成")

    def migrate_database(self):
        self.cursor.execute("PRAGMA table_info(equipment)")
        columns = [col[1] for col in self.cursor.fetchall()]
        if 'remarks' not in columns:
            self.cursor.execute("ALTER TABLE equipment ADD COLUMN remarks TEXT")
            self.conn.commit()
            logging.info("数据库已更新，添加 remarks 列")

    def load_regions_and_factories(self):
        regions = sorted([row[0] for row in self.cursor.execute('SELECT name FROM regions').fetchall()])
        factories = {region: sorted([row[0] for row in self.cursor.execute('SELECT name FROM factories WHERE region_name = ?', (region,)).fetchall()]) 
                     for region in regions}
        return regions, factories

    def save_data(self, region, factory, production_line_code, equipment_list, install_time, remarks, edit_id=None):
        add_time = datetime.now().strftime(DATETIME_FORMAT)
        try:
            with self.conn:
                if edit_id:
                    self.cursor.execute('UPDATE equipment SET region = ?, factory = ?, production_line_code = ?, install_time = ?, add_time = ?, remarks = ? WHERE id = ?', 
                                        (region, factory, production_line_code, install_time, add_time, remarks, edit_id))
                    self.cursor.execute('DELETE FROM equipment_details WHERE equipment_id = ?', (edit_id,))
                else:
                    self.cursor.execute('INSERT INTO equipment (region, factory, production_line_code, install_time, add_time, remarks) VALUES (?, ?, ?, ?, ?, ?)', 
                                        (region, factory, production_line_code, install_time, add_time, remarks))
                    edit_id = self.cursor.lastrowid

                self.cursor.executemany('INSERT INTO equipment_details (equipment_id, name, quantity) VALUES (?, ?, ?)', 
                                       [(edit_id, name, qty) for name, qty in equipment_list])
            logging.info(f"保存设备信息，ID: {edit_id}")
            return edit_id
        except sqlite3.Error as e:
            logging.error(f"保存数据失败: {e}")
            raise

    def fetch_equipment_list(self):
        self.cursor.execute('SELECT e.id, e.region, e.factory, e.production_line_code, GROUP_CONCAT(ed.name || \' *\' || ed.quantity, \', \'), e.install_time, e.add_time, e.remarks '
                            'FROM equipment e LEFT JOIN equipment_details ed ON e.id = ed.equipment_id '
                            'GROUP BY e.id')
        return self.cursor.fetchall()

    def get_equipment_details(self, equipment_id):
        return self.cursor.execute('SELECT name, quantity FROM equipment_details WHERE equipment_id = ?', (equipment_id,)).fetchall()

    def edit_equipment(self, equipment_id):
        return self.cursor.execute('SELECT * FROM equipment WHERE id = ?', (equipment_id,)).fetchone()

    def delete_equipment(self, equipment_id):
        try:
            with self.conn:
                # 先删除设备详情表中的记录
                self.cursor.execute('DELETE FROM equipment_details WHERE equipment_id = ?', (equipment_id,))
                # 再删除设备表中的记录
                self.cursor.execute('DELETE FROM equipment WHERE id = ?', (equipment_id,))
            logging.info(f"删除设备信息，ID: {equipment_id}")
        except sqlite3.Error as e:
            logging.error(f"删除设备失败: {e}")
            raise

    def add_region(self, region):
        try:
            with self.conn:
                self.cursor.execute('INSERT INTO regions (name) VALUES (?)', (region,))
            logging.info(f"添加区域: {region}")
            return True
        except sqlite3.IntegrityError:
            return False

    def add_factory(self, region, factory):
        try:
            with self.conn:
                self.cursor.execute('INSERT INTO factories (region_name, name) VALUES (?, ?)', (region, factory))
            logging.info(f"添加工厂 {factory} 到区域 {region}")
            return True
        except sqlite3.IntegrityError:
            return False

    def delete_region(self, region):
        with self.conn:
            self.cursor.execute('DELETE FROM regions WHERE name = ?', (region,))
            self.cursor.execute('DELETE FROM factories WHERE region_name = ?', (region,))
        logging.info(f"删除区域: {region}")

    def delete_factory(self, region, factory):
        with self.conn:
            self.cursor.execute('DELETE FROM factories WHERE region_name = ? AND name = ?', (region, factory))
        logging.info(f"删除工厂 {factory} 从区域 {region}")

    def query_data(self, region, factory, production_line_code, equipment):
        query = ('SELECT e.id, e.region, e.factory, e.production_line_code, GROUP_CONCAT(ed.name || \' *\' || ed.quantity, \', \'), e.install_time, e.add_time, e.remarks '
                 'FROM equipment e LEFT JOIN equipment_details ed ON e.id = ed.equipment_id WHERE 1=1')
        params = []
        if region:
            query += ' AND e.region = ?'
            params.append(region)
        if factory:
            query += ' AND e.factory = ?'
            params.append(factory)
        if production_line_code:
            query += ' AND e.production_line_code = ?'
            params.append(production_line_code)
        if equipment:
            query += ' AND ed.name = ?'
            params.append(equipment)
        query += ' GROUP BY e.id'
        self.cursor.execute(query, params)
        return self.cursor.fetchall()

    def export_data(self, selected_format, displayed_data, file_path):
        try:
            columns = ["序号", "区域", "工厂", "产线编号", "设备", "添加时间", "安装时间", "备注"]
            df = pd.DataFrame(displayed_data, columns=columns)
            if selected_format == "excel":
                df.to_excel(file_path, index=False)
                logging.info(f"数据导出为 Excel 文件: {file_path}")
                return True
            elif selected_format == "txt":
                df.to_csv(file_path, sep='\t', index=False)
                logging.info(f"数据导出为 TXT 文件: {file_path}")
                return True
        except Exception as e:
            logging.error(f"导出数据失败: {e}")
            return False

    def close(self):
        self.conn.close()
class EquipmentInfoApp:
    def __init__(self, root):
        self.root = root
        self.root.title(f"设备信息登记 V{CURRENT_VERSION}     [2025] 谭宝龙-广州物码")
        self.root.configure(bg="#f0f0f0")
        
        # 加载配置
        self.config = load_config()
        
        try:
            self.root.iconbitmap("app_icon.ico")
        except tk.TclError as e:
            logging.error(f"设置图标失败: {e}")

        # 设置窗口初始大小
        self.root.geometry(f"{self.config['width']}x{self.config['height']}")
        self.root.minsize(800, 600)

        # 设置现代化字体
        default_font = tkFont.Font(family="Microsoft YaHei", size=10)
        title_font = tkFont.Font(family="Microsoft YaHei", size=12, weight="bold")
        self.root.option_add("*Font", default_font)

        # 配置现代化主题
        self.style = ttk.Style()
        self.style.theme_use(self.config['theme'])
        
        # 自定义现代化样式
        self.style.configure("Modern.TButton",
                           padding=5,
                           font=("Microsoft YaHei", 10))
        
        self.style.configure("Modern.TLabel",
                           font=("Microsoft YaHei", 10))
        
        self.style.configure("Modern.TLabelframe",
                           font=("Microsoft YaHei", 10, "bold"),
                           padding=10)
        
        self.style.configure("Modern.TLabelframe.Label",
                           font=("Microsoft YaHei", 10, "bold"))
        
        self.style.configure("Modern.TEntry",
                           padding=5)
        
        self.style.configure("Modern.TCombobox",
                           padding=5)
        
        self.style.configure("Treeview",
                           font=("Microsoft YaHei", 10),
                           rowheight=25)
        
        self.style.configure("Treeview.Heading",
                           font=("Microsoft YaHei", 10, "bold"))
        
        # 设置树形视图的样式
        self.style.map("Treeview",
                      background=[("selected", "#0078D7")],
                      foreground=[("selected", "white")])
        
        # 设置按钮悬停效果
        self.style.map("Modern.TButton",
                      background=[("active", "#0078D7")],
                      foreground=[("active", "white")])
        
        # 设置输入框焦点效果
        self.style.map("Modern.TEntry",
                      bordercolor=[("focus", "#0078D7")])
        
        self.style.map("Modern.TCombobox",
                      bordercolor=[("focus", "#0078D7")])
        
        self.db = DatabaseManager()
        self.edit_id = None
        self.key = generate_or_load_key()
        self.cipher = Fernet(self.key)
        self.ftp_config = load_ftp_config()
        self.last_query_result = None
        self.upload_password = "admin123"

        # 创建主框架
        main_frame = ttk.Frame(self.root, style="Modern.TFrame")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 主布局：垂直分隔
        self.main_pane = ttk.PanedWindow(main_frame, orient=tk.VERTICAL)
        self.main_pane.pack(fill=tk.BOTH, expand=True)

        # 上部分：输入区和详情区（水平分隔）
        self.top_pane = ttk.PanedWindow(self.main_pane, orient=tk.HORIZONTAL)
        self.main_pane.add(self.top_pane, weight=1)

        # 输入区
        input_frame = ttk.LabelFrame(self.top_pane, text="设备信息输入", padding=10, style="Modern.TLabelframe")
        self.top_pane.add(input_frame, weight=1)
        input_frame.grid_columnconfigure(1, weight=1)
        input_frame.grid_rowconfigure(2, weight=1)

        # 输入字段
        ttk.Label(input_frame, text="区域:", style="Modern.TLabel").grid(row=0, column=0, padx=5, pady=5, sticky="e")
        self.combobox_region = ttk.Combobox(input_frame, style="Modern.TCombobox")
        self.combobox_region.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        self.combobox_region.bind("<<ComboboxSelected>>", self.on_region_change)

        ttk.Label(input_frame, text="工厂:", style="Modern.TLabel").grid(row=0, column=2, padx=5, pady=5, sticky="e")
        self.combobox_factory = ttk.Combobox(input_frame, style="Modern.TCombobox")
        self.combobox_factory.grid(row=0, column=3, padx=5, pady=5, sticky="ew")

        ttk.Label(input_frame, text="产线编号:", style="Modern.TLabel").grid(row=1, column=0, padx=5, pady=5, sticky="e")
        self.entry_production_line_code = ttk.Entry(input_frame, style="Modern.TEntry")
        self.entry_production_line_code.grid(row=1, column=1, padx=5, pady=5, sticky="ew")

        ttk.Label(input_frame, text="安装时间:", style="Modern.TLabel").grid(row=1, column=2, padx=5, pady=5, sticky="e")
        self.entry_install_time = DateEntry(input_frame, background='#0078D7', foreground='white', borderwidth=2, locale='zh_CN')
        self.entry_install_time.grid(row=1, column=3, padx=5, pady=5, sticky="ew")

        ttk.Label(input_frame, text="设备列表:", style="Modern.TLabel").grid(row=2, column=0, padx=5, pady=5, sticky="ne")
        self.equipment_pane = ttk.PanedWindow(input_frame, orient=tk.HORIZONTAL)
        self.equipment_pane.grid(row=2, column=1, columnspan=3, padx=5, pady=5, sticky="nsew")
        self.listbox_equipment = tk.Listbox(self.equipment_pane, height=5, bg="#ffffff", relief="flat", borderwidth=1, font=("Microsoft YaHei", 10))
        self.equipment_pane.add(self.listbox_equipment, weight=1)
        equipment_scroll = ttk.Scrollbar(input_frame, orient="vertical", command=self.listbox_equipment.yview)
        equipment_scroll.grid(row=2, column=4, sticky='ns', pady=5)
        self.listbox_equipment.configure(yscrollcommand=equipment_scroll.set)

        ttk.Label(input_frame, text="备注:", style="Modern.TLabel").grid(row=3, column=0, padx=5, pady=5, sticky="e")
        self.entry_remarks = ttk.Entry(input_frame, style="Modern.TEntry")
        self.entry_remarks.grid(row=3, column=1, columnspan=3, padx=5, pady=5, sticky="ew")

        # 按钮区
        button_frame = ttk.Frame(input_frame)
        button_frame.grid(row=4, column=0, columnspan=5, pady=10, sticky="ew")
        for col in range(5):
            button_frame.grid_columnconfigure(col, weight=1)
        ttk.Button(button_frame, text="添加设备", command=self.add_equipment_content, style="Modern.TButton").grid(row=0, column=0, padx=2, sticky='ew')
        ttk.Button(button_frame, text="编辑设备", command=self.edit_equipment_content, style="Modern.TButton").grid(row=0, column=1, padx=2, sticky='ew')
        ttk.Button(button_frame, text="删除设备", command=self.delete_equipment_content, style="Modern.TButton").grid(row=0, column=2, padx=2, sticky='ew')
        ttk.Button(button_frame, text="保存", command=self.save_data, style="Modern.TButton").grid(row=0, column=3, padx=2, sticky='ew')
        ttk.Button(button_frame, text="取消", command=self.clear_inputs, style="Modern.TButton").grid(row=0, column=4, padx=2, sticky='ew')

        # 详情区
        detail_frame = ttk.LabelFrame(self.top_pane, text="设备详情", padding=10, style="Modern.TLabelframe")
        self.top_pane.add(detail_frame, weight=1)
        detail_frame.grid_rowconfigure(0, weight=1)
        detail_frame.grid_columnconfigure(0, weight=1)
        self.detail_text = tk.Text(detail_frame, height=6, bg="#f5f5f5", relief="flat", borderwidth=1, font=("Microsoft YaHei", 10))
        self.detail_text.grid(row=0, column=0, padx=5, pady=5, sticky='nsew')
        self.detail_text.tag_configure("bold", font=("Microsoft YaHei", 10, "bold"), foreground="#0078D7")
        self.detail_text.config(state='disabled')

        # 中部分：只包含 Treeview
        tree_frame = ttk.Frame(self.main_pane)
        self.main_pane.add(tree_frame, weight=3)
        tree_frame.grid_rowconfigure(0, weight=1)
        tree_frame.grid_columnconfigure(0, weight=1)

        # Treeview
        self.tree = ttk.Treeview(tree_frame, columns=("Serial", "Region", "Factory", "LineCode", "Equipment", "InstallTime", "AddTime", "Remarks"), 
                                show="headings", height=15, style="Treeview")
        self.tree.grid(row=0, column=0, sticky='nsew', padx=5, pady=5)
        self.tree.heading("Serial", text="序号", command=lambda: self.sort_column("Serial", False))
        self.tree.heading("Region", text="区域", command=lambda: self.sort_column("Region", False))
        self.tree.heading("Factory", text="工厂", command=lambda: self.sort_column("Factory", False))
        self.tree.heading("LineCode", text="产线编号", command=lambda: self.sort_column("LineCode", False))
        self.tree.heading("Equipment", text="设备", command=lambda: self.sort_column("Equipment", False))
        self.tree.heading("InstallTime", text="安装时间", command=lambda: self.sort_column("InstallTime", False))
        self.tree.heading("AddTime", text="添加时间", command=lambda: self.sort_column("AddTime", False))
        self.tree.heading("Remarks", text="备注", command=lambda: self.sort_column("Remarks", False))

        # 设置树形视图的列宽
        for col, width in self.config['tree_columns'].items():
            self.tree.column(col, width=width)

        self.tree.bind("<Double-1>", self.edit_equipment)
        self.tree.bind("<<TreeviewSelect>>", self.show_details)
        v_scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
        v_scrollbar.grid(row=0, column=1, sticky='ns', pady=5)
        h_scrollbar = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview)
        h_scrollbar.grid(row=1, column=0, sticky='ew', padx=5)
        self.tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)

        # 底部框架：包含操作按钮和日志
        bottom_frame = ttk.Frame(main_frame)
        bottom_frame.pack(fill=tk.X, side=tk.BOTTOM, padx=5, pady=5)

        # 操作按钮区 - 固定在底部
        action_frame = ttk.Frame(bottom_frame)
        action_frame.pack(fill=tk.X, pady=(0, 5))
        for col in range(9):
            action_frame.grid_columnconfigure(col, weight=1)
        ttk.Button(action_frame, text="刷新", command=self.display_equipment_list, style="Modern.TButton").grid(row=0, column=0, padx=2, sticky='ew')
        ttk.Button(action_frame, text="查询", command=self.query_data, style="Modern.TButton").grid(row=0, column=1, padx=2, sticky='ew')
        ttk.Button(action_frame, text="删除产线", command=self.delete_equipment, style="Modern.TButton").grid(row=0, column=2, padx=2, sticky='ew')
        ttk.Button(action_frame, text="列表导出", command=self.export_data, style="Modern.TButton").grid(row=0, column=3, padx=2, sticky='ew')
        ttk.Button(action_frame, text="同步数据", command=self.sync_data, style="Modern.TButton").grid(row=0, column=4, padx=2, sticky='ew')
        ttk.Button(action_frame, text="主题", command=self.select_theme, style="Modern.TButton").grid(row=0, column=5, padx=2, sticky='ew')
        ttk.Button(action_frame, text="FTP设置", command=self.configure_ftp, style="Modern.TButton").grid(row=0, column=6, padx=2, sticky='ew')
        ttk.Button(action_frame, text="检查更新", command=self.check_update, style="Modern.TButton").grid(row=0, column=7, padx=2, sticky='ew')

        # 日志区
        log_frame = ttk.LabelFrame(bottom_frame, text="日志", padding=10, style="Modern.TLabelframe")
        log_frame.pack(fill=tk.X, pady=(5, 0))
        log_frame.grid_rowconfigure(0, weight=1)
        log_frame.grid_columnconfigure(0, weight=1)
        self.log_text = tk.Text(log_frame, height=5, bg="#f5f5f5", fg="#0078D7", relief="flat", borderwidth=1, font=("Microsoft YaHei", 10))
        self.log_text.grid(row=0, column=0, padx=5, pady=5, sticky='nsew')
        log_scroll = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
        log_scroll.grid(row=0, column=1, sticky='ns', pady=5)
        self.log_text.configure(yscrollcommand=log_scroll.set)
        self.log_text.config(state='disabled')

        self.equipment_list = []
        self.regions, self.factories = self.db.load_regions_and_factories()
        self.combobox_region['values'] = self.regions
        self.display_equipment_list()

        # 应用保存的布局
        self.root.after(100, self.apply_saved_layout)
        
        # 绑定窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.root.bind("<Configure>", self.on_window_configure)
        self.root.after(1000, self.refresh_log)
    def apply_saved_layout(self):
        """应用保存的布局设置"""
        try:
            # 设置窗格位置
            pane_positions = self.config['pane_positions']
            
            # 设置主面板分隔位置
            for i, pos in enumerate(pane_positions['main_pane']):
                if i < len(self.main_pane.panes()) - 1:
                    self.main_pane.after(100, lambda i=i, pos=pos: self.main_pane.sashpos(i, pos))
            
            # 设置顶部面板分隔位置
            for i, pos in enumerate(pane_positions['top_pane']):
                if i < len(self.top_pane.panes()) - 1:
                    self.top_pane.after(100, lambda i=i, pos=pos: self.top_pane.sashpos(i, pos))
            
            # 设置设备面板分隔位置
            for i, pos in enumerate(pane_positions['equipment_pane']):
                if i < len(self.equipment_pane.panes()) - 1:
                    self.equipment_pane.after(100, lambda i=i, pos=pos: self.equipment_pane.sashpos(i, pos))
            
            logging.info("布局应用成功")
        except Exception as e:
            logging.error(f"应用布局失败: {e}")

    def save_layout(self):
        """保存当前布局设置"""
        try:
            # 保存窗格位置
            self.config['pane_positions'] = {
                'main_pane': [self.main_pane.sashpos(i) for i in range(len(self.main_pane.panes())-1)],
                'top_pane': [self.top_pane.sashpos(i) for i in range(len(self.top_pane.panes())-1)],
                'equipment_pane': [self.equipment_pane.sashpos(i) for i in range(len(self.equipment_pane.panes())-1)]
            }
            
            # 保存树形视图列宽
            self.config['tree_columns'] = {
                col: self.tree.column(col, "width")
                for col in self.tree["columns"]
            }
            
            # 保存窗口大小
            self.config['width'] = self.root.winfo_width()
            self.config['height'] = self.root.winfo_height()
            
            # 保存主题
            self.config['theme'] = self.style.theme_use()
            
            # 保存配置到文件
            save_config(self.config)
            messagebox.showinfo("成功", "布局已保存")
            logging.info("布局配置已保存")
            
        except Exception as e:
            logging.error(f"保存布局失败: {e}")
            messagebox.showerror("错误", f"保存布局失败: {e}")

    def on_window_configure(self, event):
        """窗口大小改变事件处理"""
        if event.widget == self.root:
            self.adjust_tree_columns(None)

    def adjust_tree_columns(self, event):
        """调整树形视图列宽"""
        total_width = self.tree.winfo_width()
        if total_width <= 0:
            return
        
        # 更新列宽配置
        for col in self.tree["columns"]:
            width = self.tree.column(col, "width")
            self.config['tree_columns'][col] = width

    def display_equipment_list(self):
        self.tree.delete(*self.tree.get_children())
        self.last_query_result = None
        rows = self.db.fetch_equipment_list()
        for index, row in enumerate(rows, 1):
            display_row = (index, row[1], row[2], row[3], row[4], row[5], row[6], row[7])
            item = self.tree.insert("", "end", values=display_row)
            self.tree.item(item, tags=(row[0],))
        self.sort_column("Serial", False)
        self.adjust_tree_columns(None)
        self.update_log()

    def update_log(self):
        self.log_text.config(state="normal")
        self.log_text.delete(1.0, tk.END)
        try:
            with open("app.log", "r", encoding="utf-8") as f:
                self.log_text.insert(tk.END, "".join(f.readlines()[-5:]))
        except FileNotFoundError:
            self.log_text.insert(tk.END, "暂无日志记录\n")
        self.log_text.config(state="disabled")
        self.log_text.see(tk.END)

    def refresh_log(self):
        self.update_log()
        self.root.after(1000, self.refresh_log)
    def save_data(self):
        region = self.combobox_region.get().strip()
        factory = self.combobox_factory.get().strip()
        production_line_code = self.entry_production_line_code.get().strip()
        remarks = self.entry_remarks.get().strip()
        if not all([region, factory, production_line_code]):
            messagebox.showwarning("警告", "区域、工厂和产线编号不能为空！")
            return
        if not self.equipment_list:
            messagebox.showwarning("警告", "请至少添加一个设备！")
            return
        for name, qty in self.equipment_list:
            if qty <= 0:
                messagebox.showwarning("警告", "设备数量必须为正整数！")
                return
        install_time = self.entry_install_time.get_date().strftime(DATE_FORMAT)

        if region not in self.regions and len(region) <= 20:
            if self.db.add_region(region):
                self.regions.append(region)
                self.factories[region] = []
                self.combobox_region['values'] = self.regions

        if factory not in self.factories.get(region, []) and len(factory) <= 20:
            if self.db.add_factory(region, factory):
                self.factories[region].append(factory)
                self.combobox_factory['values'] = self.factories[region]

        try:
            new_id = self.db.save_data(region, factory, production_line_code, self.equipment_list, install_time, remarks, self.edit_id)
            self.clear_inputs()
            if self.last_query_result is None:
                self.display_equipment_list()
            else:
                self.display_query_result()
            self.jump_to_equipment(new_id)
            messagebox.showinfo("成功", "数据保存成功!")
            self.edit_id = None
            self.update_log()
        except Exception as e:
            messagebox.showerror("错误", f"保存数据失败: {e}")

    def jump_to_equipment(self, equipment_id):
        for item in self.tree.get_children():
            if int(self.tree.item(item, "tags")[0]) == equipment_id:
                self.tree.see(item)
                self.tree.selection_set(item)
                break

    def edit_equipment(self, event):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请选择要编辑的设备记录")
            return
        equipment_id = self.tree.item(selected[0], "tags")[0]
        row = self.db.edit_equipment(equipment_id)

        self.combobox_region.set(row[1])
        self.update_factory_combobox(row[1])
        self.combobox_factory.set(row[2])
        self.entry_production_line_code.delete(0, tk.END)
        self.entry_production_line_code.insert(0, row[3])
        self.equipment_list = self.db.get_equipment_details(equipment_id)
        self.listbox_equipment.delete(0, tk.END)
        for name, qty in self.equipment_list:
            self.listbox_equipment.insert(tk.END, f"{name} *{qty}")
        self.entry_install_time.set_date(datetime.strptime(row[4], DATE_FORMAT))
        self.entry_remarks.delete(0, tk.END)
        self.entry_remarks.insert(0, row[6] if row[6] else "")
        self.edit_id = equipment_id

    def delete_equipment(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请选择要删除的产线记录")
            return
        
        # 获取选中项的信息用于确认
        item = selected[0]
        values = self.tree.item(item)['values']
        equipment_id = int(self.tree.item(item)['tags'][0])  # 获取正确的设备ID
        
        if messagebox.askyesno("确认", f"确定要删除产线 '{values[3]}' 的记录吗？"):  # values[3] 是产线编号
            try:
                self.db.delete_equipment(equipment_id)
                if self.last_query_result is None:
                    self.display_equipment_list()
                else:
                    self.last_query_result = [row for row in self.last_query_result if row[0] != equipment_id]
                    self.display_query_result()
                messagebox.showinfo("成功", "产线记录删除成功!")
                self.update_log()
            except Exception as e:
                messagebox.showerror("错误", f"删除失败: {e}")
                logging.error(f"删除产线失败: {e}")
            self.update_log()

    def clear_inputs(self):
        self.combobox_region.set("")
        self.combobox_factory.set("")
        self.entry_production_line_code.delete(0, tk.END)
        self.entry_install_time.set_date(datetime.today())
        self.equipment_list.clear()
        self.listbox_equipment.delete(0, tk.END)
        self.entry_remarks.delete(0, tk.END)
        self.edit_id = None

    def add_equipment_content(self):
        dialog = self.create_dialog("添加设备", 300, 150)
        dialog.grid_columnconfigure(1, weight=1)
        tk.Label(dialog, text="设备名称:").grid(row=0, column=0, padx=10, pady=5)
        entry_name = ttk.Combobox(dialog, values=[row[0] for row in self.db.cursor.execute('SELECT DISTINCT name FROM equipment_details').fetchall()])
        entry_name.grid(row=0, column=1, padx=10, pady=5, sticky='ew')
        tk.Label(dialog, text="数量:").grid(row=1, column=0, padx=10, pady=5)
        entry_quantity = ttk.Entry(dialog)
        entry_quantity.grid(row=1, column=1, padx=10, pady=5, sticky='ew')

        def confirm_add():
            name, qty = entry_name.get().strip(), entry_quantity.get()
            try:
                qty = int(qty)
                if name and qty > 0:
                    self.equipment_list.append((name, qty))
                    self.listbox_equipment.insert(tk.END, f"{name} *{qty}")
                    dialog.destroy()
            except ValueError:
                messagebox.showwarning("警告", "数量必须为正整数")

        ttk.Button(dialog, text="确定", command=confirm_add).grid(row=2, column=0, columnspan=2, pady=10, sticky='ew')
        dialog.bind("<Return>", lambda e: confirm_add())
        entry_name.focus_set()
    def edit_equipment_content(self):
        selected = self.listbox_equipment.curselection()
        if not selected:
            messagebox.showwarning("警告", "请选择要编辑的设备")
            return
        index = selected[0]
        name, qty = self.equipment_list[index]

        dialog = self.create_dialog("编辑设备", 300, 150)
        dialog.grid_columnconfigure(1, weight=1)
        tk.Label(dialog, text="设备名称:").grid(row=0, column=0, padx=10, pady=5)
        entry_name = ttk.Entry(dialog)
        entry_name.grid(row=0, column=1, padx=10, pady=5, sticky='ew')
        entry_name.insert(0, name)
        tk.Label(dialog, text="数量:").grid(row=1, column=0, padx=10, pady=5)
        entry_quantity = ttk.Entry(dialog)
        entry_quantity.grid(row=1, column=1, padx=10, pady=5, sticky='ew')
        entry_quantity.insert(0, qty)

        def confirm_edit():
            name, qty = entry_name.get().strip(), entry_quantity.get()
            try:
                qty = int(qty)
                if name and qty > 0:
                    self.equipment_list[index] = (name, qty)
                    self.listbox_equipment.delete(index)
                    self.listbox_equipment.insert(index, f"{name} *{qty}")
                    dialog.destroy()
            except ValueError:
                messagebox.showwarning("警告", "数量必须为正整数")

        ttk.Button(dialog, text="确定", command=confirm_edit).grid(row=2, column=0, columnspan=2, pady=10, sticky='ew')
        dialog.bind("<Return>", lambda e: confirm_edit())
        entry_name.focus_set()

    def delete_equipment_content(self):
        selected = self.listbox_equipment.curselection()
        if selected and messagebox.askyesno("确认", f"确定删除 '{self.listbox_equipment.get(selected[0])}'？"):
            self.equipment_list.pop(selected[0])
            self.listbox_equipment.delete(selected[0])
            self.update_log()

    def show_details(self, event):
        selected = self.tree.selection()
        self.detail_text.config(state="normal")
        self.detail_text.delete(1.0, tk.END)
        if not selected:
            self.detail_text.config(state="disabled")
            return

        equipment_id = self.tree.item(selected[0], "tags")[0]
        row = self.db.edit_equipment(equipment_id)
        details = self.db.get_equipment_details(equipment_id)

        self.detail_text.insert(tk.END, "详细信息:\n")
        for label, value in [("ID", row[0]), ("区域", row[1]), ("工厂", row[2]), ("产线编号", row[3])]:
            self.detail_text.insert(tk.END, f"{label}: ", "bold")
            self.detail_text.insert(tk.END, f"{value}\n")
        self.detail_text.insert(tk.END, "设备: ", "bold")
        self.detail_text.insert(tk.END, ", ".join(f"{name}*{qty}" for name, qty in details) + "\n")
        for label, value in [("安装时间", row[4]), ("添加时间", row[5]), ("备注", row[6] or "无")]:
            self.detail_text.insert(tk.END, f"{label}: ", "bold")
            self.detail_text.insert(tk.END, f"{value}\n")
        self.detail_text.config(state="disabled")

    def create_dialog(self, title, width, height):
        dialog = tk.Toplevel(self.root)
        dialog.title(title)
        dialog.geometry(f"{width}x{height}+{self.root.winfo_x()+50}+{self.root.winfo_y()+50}")
        dialog.transient(self.root)
        dialog.grab_set()
        try:
            dialog.iconbitmap("app_icon.ico")
        except tk.TclError as e:
            logging.error(f"设置对话框图标失败: {e}")
        return dialog

    def configure_ftp(self):
        dialog = self.create_dialog("FTP配置", 400, 300)
        dialog.grid_columnconfigure(1, weight=1)
        fields = [("主机地址:", "host"), ("用户名:", "username"), ("密码:", "password", True), ("端口:", "port"), ("远程路径:", "path")]
        entries = {}
        for i, (label, key, *args) in enumerate(fields):
            tk.Label(dialog, text=label).grid(row=i, column=0, padx=10, pady=5)
            entry = ttk.Entry(dialog, show="*" if args else "")
            entry.grid(row=i, column=1, padx=10, pady=5, sticky='ew')
            entry.insert(0, self.ftp_config.get(key, 21 if key == "port" else "/equipment" if key == "path" else ""))
            entries[key] = entry

        def test_ftp():
            success, msg = test_ftp_connection(entries["host"].get(), entries["username"].get(), entries["password"].get(), entries["port"].get(), entries["path"].get())
            messagebox.showinfo("测试结果", msg)

        def save_ftp():
            self.ftp_config = {key: entries[key].get().strip() for key in entries}
            save_ftp_config(self.ftp_config)
            messagebox.showinfo("成功", "FTP配置已保存")
            dialog.destroy()
            self.update_log()

        ttk.Button(dialog, text="测试", command=test_ftp).grid(row=5, column=0, pady=10, sticky='ew')
        ttk.Button(dialog, text="保存", command=save_ftp).grid(row=5, column=1, pady=10, sticky='ew')
        entries["host"].focus_set()
    def sync_data(self):
        if not all(self.ftp_config.get(k, "") for k in ["host", "username", "password", "path"]):
            messagebox.showwarning("警告", "请先配置FTP设置")
            return

        dialog = self.create_dialog("同步数据", 300, 200)
        def upload_to_ftp():
            pwd_dialog = self.create_dialog("上传验证", 300, 150)
            pwd_dialog.grid_columnconfigure(1, weight=1)
            tk.Label(pwd_dialog, text="上传密码:").pack(pady=10)
            entry_pwd = ttk.Entry(pwd_dialog, show="*")
            entry_pwd.pack(pady=5, fill=tk.X, padx=20)

            def verify_and_upload():
                if entry_pwd.get() == self.upload_password:
                    try:
                        ftp = FTP()
                        ftp.connect(self.ftp_config["host"], int(self.ftp_config["port"]))
                        ftp.login(self.ftp_config["username"], self.ftp_config["password"])
                        ftp.cwd(self.ftp_config["path"])
                        with open("equipment_info.db", "rb") as f:
                            ftp.storbinary("STOR equipment_info.db", f)
                        ftp.quit()
                        messagebox.showinfo("成功", "数据库已上传")
                        self.update_log()
                    except Exception as e:
                        messagebox.showerror("错误", f"上传失败: {e}")
                    pwd_dialog.destroy()
                    dialog.destroy()
                else:
                    messagebox.showerror("错误", "密码错误")
                    pwd_dialog.destroy()

            ttk.Button(pwd_dialog, text="确定", command=verify_and_upload).pack(pady=10, fill=tk.X, padx=20)
            entry_pwd.focus_set()

        def download_from_ftp():
            try:
                ftp = FTP()
                ftp.connect(self.ftp_config["host"], int(self.ftp_config["port"]))
                ftp.login(self.ftp_config["username"], self.ftp_config["password"])
                ftp.cwd(self.ftp_config["path"])
                with open("equipment_info.db.download", "wb") as f:
                    ftp.retrbinary("RETR equipment_info.db", f.write)
                ftp.quit()
                if messagebox.askyesno("确认", "即将覆盖本地数据库，需重启。继续？"):
                    self.db.close()
                    import time
                    time.sleep(0.5)
                    os.replace("equipment_info.db.download", "equipment_info.db")
                    messagebox.showinfo("成功", "数据库已更新，程序重启")
                    self.root.destroy()
                    os.execl(sys.executable, sys.executable, *sys.argv)
                else:
                    os.remove("equipment_info.db.download")
            except Exception as e:
                messagebox.showerror("错误", f"下载失败: {e}")
                if os.path.exists("equipment_info.db.download"):
                    os.remove("equipment_info.db.download")
            dialog.destroy()

        ttk.Button(dialog, text="上传", command=upload_to_ftp).pack(pady=10, fill=tk.X, padx=20)
        ttk.Button(dialog, text="下载", command=download_from_ftp).pack(pady=10, fill=tk.X, padx=20)

    def check_update(self):
        if not all(self.ftp_config.get(k, "") for k in ["host", "username", "password"]):
            messagebox.showwarning("警告", "请先配置FTP设置")
            return
        try:
            ftp = FTP()
            ftp.connect(self.ftp_config["host"], int(self.ftp_config["port"]))
            ftp.login(self.ftp_config["username"], self.ftp_config["password"])
            ftp.cwd(self.ftp_config.get("path", "/update"))
            with open("temp_version.txt", "wb") as f:
                ftp.retrbinary("RETR version.txt", f.write)
            with open("temp_version.txt", "r") as f:
                latest_version = f.read().strip()
            ftp.quit()
            os.remove("temp_version.txt")

            if latest_version > CURRENT_VERSION and messagebox.askyesno("更新", f"发现新版本 {latest_version}，下载？"):
                self.download_update()
            else:
                messagebox.showinfo("更新", "已是最新版本")
            self.update_log()
        except Exception as e:
            messagebox.showerror("错误", f"检查更新失败: {e}")

    def download_update(self):
        try:
            ftp = FTP()
            ftp.connect(self.ftp_config["host"], int(self.ftp_config["port"]))
            ftp.login(self.ftp_config["username"], self.ftp_config["password"])
            ftp.cwd(self.ftp_config.get("path", "/update"))
            local_path = os.path.join(os.getcwd(), "Equipment Register.exe")
            with open(local_path, "wb") as f:
                ftp.retrbinary("RETR Equipment Register.exe", f.write)
            ftp.quit()
            messagebox.showinfo("成功", "更新已下载，程序将重启")
            self.db.close()
            import time
            time.sleep(1)
            os.startfile(local_path)
            self.root.destroy()
        except Exception as e:
            messagebox.showerror("错误", f"下载更新失败: {e}")

    def query_data(self):
        dialog = self.create_dialog("查询数据", 400, 200)  # 初始高度设置小一些
        dialog.grid_columnconfigure(1, weight=1)
        
        # 创建区域选择框架
        region_frame = ttk.LabelFrame(dialog, text="区域选择", padding=5)
        region_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=5, sticky='ew')
        region_frame.grid_columnconfigure(0, weight=1)
        
        # 创建工厂选择框架
        factory_frame = ttk.LabelFrame(dialog, text="工厂选择", padding=5)
        factory_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=5, sticky='ew')
        factory_frame.grid_columnconfigure(0, weight=1)
        
        # 区域单选按钮
        region_var = tk.StringVar()
        region_buttons = []
        region_frame_inner = ttk.Frame(region_frame)
        region_frame_inner.pack(fill='x', padx=5, pady=5)
        
        for i, region in enumerate(self.regions):
            btn = ttk.Radiobutton(region_frame_inner, text=region, value=region, variable=region_var)
            btn.pack(side='left', padx=5, pady=2)
            region_buttons.append(btn)
            if i == 0:
                btn.invoke()  # 默认选中第一个
        
        # 工厂单选按钮
        factory_var = tk.StringVar()
        factory_buttons = []
        factory_frame_inner = ttk.Frame(factory_frame)
        factory_frame_inner.pack(fill='x', padx=5, pady=5)
        
        def update_factory_buttons(*args):
            # 清除现有的工厂按钮
            for btn in factory_buttons:
                btn.destroy()
            factory_buttons.clear()
            
            # 获取选中的区域
            selected_region = region_var.get()
            if selected_region:
                # 创建新的工厂按钮
                factories = self.factories.get(selected_region, [])
                for i, factory in enumerate(factories):
                    btn = ttk.Radiobutton(factory_frame_inner, text=factory, value=factory, variable=factory_var)
                    btn.pack(side='left', padx=5, pady=2)
                    factory_buttons.append(btn)
                    if i == 0:
                        btn.invoke()  # 默认选中第一个
                
                # 动态调整窗口高度
                dialog.update_idletasks()  # 更新窗口状态
                region_height = region_frame.winfo_reqheight()
                factory_height = factory_frame.winfo_reqheight()
                button_height = 40  # 查询按钮的高度
                padding = 20  # 上下padding
                total_height = region_height + factory_height + button_height + padding
                dialog.geometry(f"400x{total_height}")
        
        # 绑定区域选择变化事件
        region_var.trace('w', update_factory_buttons)
        update_factory_buttons()  # 初始化工厂按钮

        def perform_query():
            self.last_query_result = self.db.query_data(region_var.get(), factory_var.get(), "", "")
            self.display_query_result()
            dialog.destroy()

        ttk.Button(dialog, text="查询", command=perform_query).grid(row=2, column=0, columnspan=2, pady=10, sticky='ew')

    def display_query_result(self):
        self.tree.delete(*self.tree.get_children())
        if self.last_query_result:
            for index, row in enumerate(self.last_query_result, 1):
                display_row = (index, row[1], row[2], row[3], row[4], row[5], row[6], row[7])
                item = self.tree.insert("", "end", values=display_row)
                self.tree.item(item, tags=(row[0],))
            self.sort_column("Serial", False)
        self.adjust_tree_columns(None)
        self.update_log()

    def export_data(self):
        dialog = self.create_dialog("导出数据  *导出列表中显示的全部数据", 400, 150)
        dialog.grid_columnconfigure(1, weight=1)
        tk.Label(dialog, text="格式:").grid(row=0, column=0, padx=10, pady=5)
        format_var = tk.StringVar(value="excel")
        ttk.Combobox(dialog, values=["excel", "txt"], textvariable=format_var).grid(row=0, column=1, padx=10, pady=5, sticky='ew')
        tk.Label(dialog, text="路径:").grid(row=1, column=0, padx=10, pady=5)
        path_var = tk.StringVar(value=os.getcwd())
        ttk.Entry(dialog, textvariable=path_var).grid(row=1, column=1, padx=10, pady=5, sticky='ew')
        ttk.Button(dialog, text="浏览", command=lambda: path_var.set(filedialog.askdirectory())).grid(row=1, column=2, padx=5, sticky='ew')

        def perform_export():
            displayed_data = [self.tree.item(item, "values") for item in self.tree.get_children()]
            file_name = f"equipment_info.{format_var.get() == 'excel' and 'xlsx' or 'txt'}"
            full_path = os.path.join(path_var.get(), file_name)
            if self.db.export_data(format_var.get(), displayed_data, full_path):
                messagebox.showinfo("成功", f"数据已导出至 {full_path}")
                self.update_log()
            else:
                messagebox.showerror("错误", "导出失败")
            dialog.destroy()

        ttk.Button(dialog, text="导出", command=perform_export).grid(row=2, column=0, columnspan=3, pady=10, sticky='ew')

    def select_theme(self):
        dialog = self.create_dialog("选择主题", 300, 200)
        theme_var = tk.StringVar(value=self.style.theme_use())
        for theme in ["default", "clam", "alt", "classic", "vista", "xpnative"]:
            ttk.Radiobutton(dialog, text=theme.capitalize(), value=theme, variable=theme_var, 
                            command=lambda t=theme: self.apply_theme(t)).pack(anchor="w", padx=20, pady=2)
        ttk.Button(dialog, text="关闭", command=dialog.destroy).pack(pady=10, fill=tk.X, padx=20)

    def apply_theme(self, theme):
        self.style.theme_use(theme)
        bg_colors = {"default": "#f0f0f0", "clam": "#e0e0e0", "alt": "#d9d9d9"}
        date_colors = {"default": "#4CAF50", "clam": "#008080", "alt": "#4682b4"}
        self.root.configure(bg=bg_colors.get(theme, "#f0f0f0"))
        if hasattr(self, 'entry_install_time'):
            self.entry_install_time.configure(background=date_colors.get(theme, "#4CAF50"))
        self.config['theme'] = theme
        save_config(self.config)

    def update_factory_combobox(self, region):
        self.combobox_factory['values'] = self.factories.get(region, [])
        self.combobox_factory.set("")

    def on_region_change(self, event):
        self.update_factory_combobox(self.combobox_region.get())

    def sort_column(self, col, reverse):
        data = [(self.tree.set(item, col), item) for item in self.tree.get_children('')]
        data.sort(key=lambda x: int(x[0]) if col == "Serial" else x[0], reverse=reverse)
        for index, (_, item) in enumerate(data):
            self.tree.move(item, '', index)
        self.tree.heading(col, command=lambda: self.sort_column(col, not reverse))

    def on_closing(self):
        try:
            self.save_layout()
            self.db.close()
            self.root.destroy()
        except Exception as e:
            logging.error(f"关闭程序时发生错误: {e}")
            self.root.destroy()

if __name__ == "__main__":
    try:
        root = tk.Tk()
        app = EquipmentInfoApp(root)
        root.mainloop()
    except Exception as e:
        logging.error(f"程序运行出错: {e}")
